/*
 * Triangle.java
 *
 * Created on September 22, 2007, 9:07 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package Raytracing.Geometry;

import Raytracing.BoundingBox;
import Raytracing.Intersectable;
import Raytracing.Ray;

import Raytracing.Shaders.Shader;
import processing.core.*;

public class Triangle extends Intersectable {

    protected static final boolean DEBUG = true;
    public float area = 0;
    public PImage texture;
    public PVector vert0, vert1, vert2;
    PVector edge1, edge2;

    float us = 1,vs = 1;
    
    public PVector sn,tn,nn;

    public float u0, u1, u2, v0, v1, v2;
    public float uspan = 0, vspan = 0;
    public float minu, minv;
    public PVector gNormal = new PVector();

    public Triangle(Shader shader, PVector vert0, PVector vert1, PVector vert2, float u0, float v0, float u1, float v1, float u2, float v2, PImage texture) {

        this.shader = shader;
        this.vert0 = vert0;
        this.vert1 = vert1;
        this.vert2 = vert2;

        //precalculate the edges
        edge1 = PVector.sub(vert1, vert0);
        edge2 = PVector.sub(vert2, vert0);


        
        this.u0 = u0;
        this.u1 = u1;
        this.u2 = u2;

        this.v0 = v0;
        this.v1 = v1;
        this.v2 = v2;

        this.texture = texture;

        minBounds = new PVector();
        maxBounds = new PVector();

        //get the bounds of this triangle
        minBounds.x = PApplet.min(vert0.x, vert1.x, vert2.x);
        minBounds.y = PApplet.min(vert0.y, vert1.y, vert2.y);
        minBounds.z = PApplet.min(vert0.z, vert1.z, vert2.z);

        maxBounds.x = PApplet.max(vert0.x, vert1.x, vert2.x);
        maxBounds.y = PApplet.max(vert0.y, vert1.y, vert2.y);
        maxBounds.z = PApplet.max(vert0.z, vert1.z, vert2.z);


        //calculate geometric normal
        edge2.cross(edge1, gNormal);
        gNormal.normalize();

        nn = gNormal.get();
        sn = edge1.get();
        sn.normalize();
        
        tn = sn.cross(gNormal);
        tn.normalize();

        PVector temp = new PVector();
        edge1.cross(edge2, temp);
        area = .5f * temp.mag();

        float maxu = PApplet.max(u0, u1, u2);
        minu = PApplet.min(u0, u1, u2);
        float maxv = PApplet.max(v0, v1, v2);
        minv = PApplet.min(v0, v1, v2);

        uspan = maxu - minu;
        vspan = maxv - minv;


    }

    public void setUVScale(float us, float vs){
        this.us = us;
        this.vs = vs;
    }
    
    
    public void draw(PApplet g) {

       
        if (texture == null) {
            g.fill(shader.fill);
            
            g.beginShape(PApplet.TRIANGLES);
            g.vertex(vert0.x, vert0.y, vert0.z);
            g.vertex(vert1.x, vert1.y, vert1.z);
            g.vertex(vert2.x, vert2.y, vert2.z);
            g.endShape();
        } else {

            g.beginShape(PApplet.TRIANGLES);
            g.texture(texture);
            g.vertex(vert0.x, vert0.y, vert0.z, u0, v0);
            g.vertex(vert1.x, vert1.y, vert1.z, u1, v1);
            g.vertex(vert2.x, vert2.y, vert2.z, u2, v2);
            g.endShape();
        }

    }

    public void draw(PApplet g, PVector camDirection) {



    }

    public boolean intersect(Ray ray) {

        PVector pVec = new PVector(), tVec, qVec = new PVector();

        float det, inv_det;

        ray.direction.cross(edge2, pVec);

        det = edge1.dot(pVec);

        //discard if parallel to plane
        if (det > -.00001 && det < .00001) {
            return false;
        }
        inv_det = 1.f / det;

        /*calculate distance from vert1 to ray origin*/
        tVec = PVector.sub(ray.origin, vert0);

        /*calculate u and test bounds*/
        ray.u = tVec.dot(pVec) * inv_det;
        if (ray.u < 0.0 || ray.u > 1.0) {
            return false;
        }

        /*prepare to test V parameter*/
        tVec.cross(edge1, qVec);

        /*calculate V and test bounds*/
        ray.v = ray.direction.dot(qVec) * inv_det;

        if (ray.v < 0.f || ray.u + ray.v > 1.f) {
            return false;
        }

        /*calculate t, ray intersects triangle */
        ray.distance = edge2.dot(qVec) * inv_det;

        /* now, we check to see if it is in a valid range */
        if (ray.distance > ray.maxDist || ray.distance < ray.minDist) {
            return false;
        }


        //generate texture coordinates

        float b = 1.0f - ray.u - ray.v;
        ray.tu = (u0 * b + u1 * ray.u + u2 * ray.v);
        ray.tv = (v0 * b + v1 * ray.u + v2 * ray.v);


        ray.tu *=us;
        ray.tv *=vs;
        
        //state.tangent1 = edge1;
        //edge1.cross(state.normal,state.tangent2);
        
        ray.normal = nn;
        ray.sn = sn;
        ray.tn = tn;
        return true;

    }

    /**
     * Gets the point in space occupied by a location on the texture surface
     *
     * @param u u coord of texture point
     * @param v v coord of texture point
     * @return 
     */
    public PVector getPoint(float u, float v) {

        float b = 1.f - u - v;

        return new PVector(vert0.x + v * edge2.x + u * edge1.x,
                vert0.y + v * edge2.y + u * edge1.y,
                vert0.z + v * edge2.z + u * edge1.z);
    }

    public boolean inside(BoundingBox bounds) {

        return ((greaterThan(maxBounds, bounds.min)) && (lessThan(minBounds, bounds.max)));
    }

    private boolean greaterThan(PVector lhs, PVector rhs) {
        if (lhs.x >= rhs.x && lhs.y >= rhs.y && lhs.z >= rhs.z) {
            return true;
        } else {
            return false;
        }
    }

    private boolean lessThan(PVector lhs, PVector rhs) {
        if (lhs.x <= rhs.x && lhs.y <= rhs.y && lhs.z <= rhs.z) {
            return true;
        } else {
            return false;
        }
    }

    public PVector worldToLocal(PVector in){

        return new PVector(in.dot(sn), in.dot(tn),in.dot(nn));


    }

    public PVector localToWorld(PVector in){

        return new PVector( sn.x*in.x + tn.x*in.y + nn.x*in.z,
                            sn.y*in.x + tn.y*in.y + nn.y*in.z,
                            sn.z*in.x + tn.z*in.y + nn.z*in.z
                );
    }


 
}
